package onetenj.examples;

import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.geometry.*;
import javafx.scene.paint.*;
import javafx.application.*;
import javafx.input.*; 
import javafx.scene.text.Text;
import javafx.ext.swing.*;
import javafx.animation.*;

/**
 * @author Omer Haderi
 */
Frame {
    title: "8 ball game!"
    width: 430 + 9
    height: 400 + 35
    closeAction: function() { 
        java.lang.System.exit( 0 ); 
    }
    visible: true
    stage: Stage {
        fill: LinearGradient {
            startX: 0, startY: .5, endX: 0, endY: 0
            stops: [
                Stop {offset: .5 color: Color.BISQUE},
                Stop {offset: 1 color: Color.CHOCOLATE},
            ]
        }
        content: GameWidget{}
    }
}

class Model {
    public static attribute STOP_BALANCE:String = "Stop Balance";
    public static attribute START_BALANCE:String = "Start Balance";
    public static attribute ORIENTATION_LEFT: Integer = -2;
    public static attribute ORIENTATION_RIGHT: Integer = 2;
    
    private attribute angle: Number = 180;
    private attribute orientation: Number = 0;
    private attribute leftWeight: Integer = 0;
    private attribute rightWeight: Integer = 0;
    private attribute majorDigitCounter: Integer;
    private attribute minorDigitCounter: Integer;
    private attribute equalText: String = "";
    
    private attribute timer: Timeline = Timeline {
        repeatCount: 45
        keyFrames : [
            KeyFrame {
                time : 10ms
                action: function() {
                    angle += orientation; 
                }
            }
        ]
    }
}

public class GameWidget extends CustomNode {
    private static attribute SPHERE_X: Number = 3;
    private static attribute SPHERE_Y: Number = 295;
    
    private attribute model: Model = Model{};
    private attribute balance: Balance = Balance {
        x: 5, y: 10
        model: bind model
        
    };
    private attribute dock: Dock = Dock {x: 5, y: 325};
    private attribute spheres: Sphere[] = Sphere {x: SPHERE_X, y: SPHERE_Y}.get8Spheres();
    private attribute balanceButton: Button = Button {
        text: Model.START_BALANCE
        action: function() { 
            if (balanceButton.text == Model.START_BALANCE) {
                balanceButton.text = Model.STOP_BALANCE;
                testBalance();
                enabledButtons(false);
                blockMouse(true);
            } else if (balanceButton.text == Model.STOP_BALANCE) {
                balanceButton.text = Model.START_BALANCE;
                initBalance();
                enabledButtons(true);
                blockMouse(false);
            }
        }
    };
    
    private attribute resetButton: Button = Button {
        text: "Reset Ball Weight"
        action: function() { 
            reset();
        }
    };
    
    private attribute winButton: Button = Button {
        text: "Show Heavy Ball"
        action: function() { 
            for (i in spheres) {
                i.showBall();
            }
        }
    };
    
    public attribute buttonLayout: HBox = HBox {
        content:[
            ComponentView {
                component: resetButton
            },
            ComponentView {
                component: balanceButton
            },
            ComponentView {
                component: winButton
            },
        ]
    }
    
    public function create(): Node {
        return Group {
            content: [balance, dock, spheres, buttonLayout]
        }
    }
    
    private function enabledButtons(flag: Boolean): Void {
        resetButton.enabled = flag;
        winButton.enabled = flag;
    }
    
    private function blockMouse(flag: Boolean): Void {
        for (i in spheres) {
            i.mouseBlock = flag;
        }
    }
    
    private function reset(): Void {
        model.minorDigitCounter = 0;
        model.majorDigitCounter = 0;
        model.angle = 180;
        model.orientation = 0;
        initBalance();
        randomWeightForSpheres();
    }
    
    public function randomWeightForSpheres(): Void {
        var weight:Integer = 1;
        var t = new java.util.Random();
        var ballNumber: Integer = t.nextInt(8) + 1;
        for (i in spheres) {
            if (ballNumber == i.sphereNumber) {
               i.weight = 2;
            } else {
                i.weight = weight;
            }
        }
    }
    
    private function increaseTestCounter(): Void {
        model.minorDigitCounter++;
        if (model.minorDigitCounter > 9) {
            model.minorDigitCounter = 0;
            model.majorDigitCounter++;
            if (model.majorDigitCounter > 9) {
                model.majorDigitCounter = 0;
                // maybe show a msg to inform that user made too many tests
                // and reset the counter! support until 99
                reset();
            }
        }
    }
    
    private function initBalance(): Void {
        model.leftWeight = 0;
        model.rightWeight = 0;
        model.equalText = "";
        if (model.angle != 180) {
            if (model.orientation > 0) {
                model.orientation = Model.ORIENTATION_LEFT;
            } else if (model.orientation < 0) {
                model.orientation = Model.ORIENTATION_RIGHT;
            }
        }
        model.timer.start();
    }
    
    private function testBalance(): Void {
        increaseTestCounter();
        model.leftWeight = 0;
        model.rightWeight = 0;
        model.orientation = 0;
        
        for (i in spheres) {
            if (balance.left.contains(i.getBoundsX(), i.getBoundsY())) {
                model.leftWeight += i.weight;
            }
            if (balance.right.contains(i.getBoundsX(), i.getBoundsY())) {
                model.rightWeight += i.weight;
            }
        }
        
        if (model.leftWeight > model.rightWeight) {
            model.orientation = Model.ORIENTATION_LEFT;
        } else if (model.leftWeight < model.rightWeight) {
            model.orientation = Model.ORIENTATION_RIGHT;
        } else {
            model.equalText = "equal";
        }
        model.timer.start();
    }
}

public class Balance extends CustomNode {
    private attribute model: Model;
    public attribute x: Number;
    public attribute y: Number;
    private attribute left: Node = createBalanceUnit(95, 110);
    private attribute right: Node = createBalanceUnit(330, 110);
    private attribute base: Node = createBase(40, 50);
    private attribute balancer: Node = createBalancer(212, 105);    
    
    private function createBalancer(x: Number, y: Number): Node {
        var numOfMarks = 4;
        return Group {
            content: [
                Rectangle {
                    x: 160, y: 50
                    width: 105, height: 120
                    arcHeight: 5, arcWidth: 5
                    fill: LinearGradient { startX:0 startY:0 endX:0 endY:1
                        stops: [
                            Stop { offset:0 color: Color.web("#3c3c3c") },
                            Stop { offset:1 color: Color.web("#010101") }
                        ]
                    }
                },
                Text {
                    font: Font { 
                        size: 13 
                        style: FontStyle.BOLD_ITALIC
                    }
                    fill: Color.GREEN
                    x: 195, y: 135
                    content: bind model.equalText
                },
                Circle {
                    centerX: x 
                    centerY: y 
                    radius: 50
                    fill: null
                    strokeWidth: 0.5
                    stroke: Color.WHITE
                },
                Circle {
                    centerX: x 
                    centerY: y 
                    radius: 48
                    fill: null
                    strokeWidth: 0.5
                    stroke: Color.WHITE
                },
                for (i in [1..numOfMarks]) {
                    Line {
                        startX: 0 startY: 52 endX: 0 endY: 42
                        stroke: Color.WHITE
                        strokeWidth: 1
                        rotate: (360 / numOfMarks) * i
                        translateX: x
                        translateY: y
                    }
                },
                Group {
                    content: [
                        Group { 
                            content: [
                                Circle {
                                    centerX: 0 centerY: 0 
                                    radius: 3
                                    fill: Color.WHITE
                                },
                                Rectangle {
                                    x: -1 y: -8 
                                    width: 2 height: 35
                                    fill: Color.WHITE
                                    rotate: bind model.angle
                                },
                            ]
                        }
                    ]
                    translateX: x translateY: y
                }
            ]
        }
    }
    
    private function createBalanceUnit(x: Number, y: Number): Node {
        var gg = Group {
            cache: true
            content: [
                Circle {
                    centerX: x
                    centerY: y
                    radius: 60
                    fill:LinearGradient { startX:0 startY:0 endX:1 endY:1
                        stops: [
                            Stop { offset:0 color: Color.web("#3c3c3c") },
                            Stop { offset:1 color: Color.web("#010101") }
                        ]
                    }
                },
                Circle {
                    centerX: x
                    centerY: y
                    radius: 54
                    fill: Color.rgb(20,20,20)
                },
            ]
        }
        return gg;
    } 
    
    private function createBase(x: Number, y: Number): Node {
        var height = 20;
        var width = 370;
        return Group {
            content: [
                Rectangle {
                    x: 10, y: 170
                    width: width, height: height
                    fill: Color.GRAY
                },
                Group {
                    content: [
                        Group {
                            translateX: 205 
                            translateY: 180
                            content: [
                                Rectangle{x: -7 y: -9 width: 15 height: 19 fill: Color.GRAY},
                                Rectangle{x: -5 y: -7 width: 11 height: 15 fill: Color.BLACK},
                            ]
                        },
                        Group {
                            translateX: 220 
                            translateY: 180
                            content: [
                                Rectangle{x: -7 y: -9 width: 15 height: 19 fill: Color.GRAY},
                                Rectangle{x: -5 y: -7 width: 11 height: 15 fill: Color.BLACK},
                            ]
                        },
                        Text {
                            x: 203 
                            y: 185
                            fill: Color.WHITE
                            font: Font { 
                                size: 12 
                                style: FontStyle.BOLD
                            }
                            content: bind model.majorDigitCounter.toString();
                        },
                        Text {
                            x: 218 
                            y: 185
                            fill: Color.WHITE
                            font: Font { 
                                size: 12 
                                style: FontStyle.BOLD
                            }
                            content: bind model.minorDigitCounter.toString();
                        }
                    ]
                },
                Polyline {
                    var x1 = x - 30;
                    var y1 = y + 120;
                    points : [x,y, x + width,y, x1 + width,y1, x1,y1]
                    fill: Color.GRAY
                    opacity: .4
                },
                Polyline {
                    var x1 = x - 30;
                    var y1 = y + 120;
                    points : [x1 + width,y1, x + width,y, x1 + width,y1 + height]
                    fill: Color.GRAY
                },
            ]
        }
    }
    
    public function create(): Node {
        return Group {
            translateX: bind x
            translateY: bind y
            content: [left, right, base, balancer]
        }
    }
}

public class Dock extends CustomNode {
    public attribute height: Number = 20;
    public attribute width: Number = 360;
    public attribute x: Number;
    public attribute y: Number;
    
    public function create(): Node {
        return Group {
            content: Polyline {
                var x1 = x + 30;// x value for point 2
                var x2 = x1 + width; // x value for point 3
                var x3 = x2 + 30; // x value for point 4
                points : [ x,y + height, x1,y, x2,y, x3,y + height, x,y + height ]
                strokeWidth: 1
                stroke: Color.BLACK
                fill: Color.GRAY
                opacity: .2
            };
        }
    }
}

public class Sphere extends CustomNode {
    public attribute x: Number;
    public attribute y: Number;
    public attribute radius: Number;
    public attribute color: Color;
    public attribute weight: Integer;
    public attribute sphereNumber: Integer;
    
    private attribute opac: Number = 1;
    private static attribute HEAVY_WEIGHT: Integer = 2;
    private attribute mouseBlock: Boolean = false;
    
    private attribute sphere: Circle = Circle {
        centerX: bind x 
        centerY: bind y 
        radius : bind radius
        fill: RadialGradient {  
            centerX: x - 20 
            centerY: y - 20
            radius: radius + 70
            proportional: false 
            stops: [  
                Stop {offset: 0.0 color: Color.WHITE},  
                Stop {offset: 0.5 color: bind color},  
                Stop {offset: 1.0 color: Color.BLACK},  
            ]  
        }
    }
    
    public function create(): Node {
        return DragGroup {
            blocksMouse: true // Group attribute 
            blockedMouse: bind mouseBlock // DragGroup attribute
            content: [this.sphere, Text {
                    fill: Color.WHITE
                    font: Font { 
                        size: 14 
                        style: FontStyle.BOLD
                    }
                    x: x - 2, y: y + 3
                    content: bind sphereNumber.toString();
                }
            ]
        }
    }
    
    private attribute timer : Timeline = Timeline {
        repeatCount: 70
        keyFrames : KeyFrame {
            time : 90ms
            action : function() : Void {
                if (radius == 20) {
                    radius++;
                } else if (radius == 21){
                    radius--;
                }
            }
        }
    };
    
    public function showBall(): Void {
        if (weight == HEAVY_WEIGHT) {
            timer.start();
        }
    }
    
    public function get8Spheres(): Sphere[] {
        var spheres: Sphere[];
        var tmp = (x - (9 / 2)) + 58;
        var c = Color.RED;
        var weight:Integer = 1;
        var t = new java.util.Random();
        var ballNumber: Integer = t.nextInt(8) + 1;
        
        for (i in [1.. 8]) {
            if (ballNumber == i) {
                weight = HEAVY_WEIGHT;
            }
            if (tmp mod 2 == 0) {
                c = Color.BLUE;
            }
            insert Sphere {
                x: tmp;
                y: y + 29;
                radius: 20;
                color: c;
                sphereNumber: i
                weight: weight
            } into spheres;
            tmp += 45;
            c = Color.RED;
            weight = 1;
        }
        return spheres;
    }
}

/**
 * Source:
 * @see http://silveiraneto.net/2008/08/11/javafx-draggable-node/
 */
public class DragGroup extends CustomNode {  
    public attribute content: Node[];  
   
    private attribute endX = 0.0;  
    private attribute endY = 0.0;  
    private attribute startX = 0.0;  
    private attribute startY = 0.0; 
    private attribute blockedMouse: Boolean = false;
   
    public function create(): Node {  
        return Group {
            translateX: bind endX  
            translateY: bind endY  
            blocksMouse: bind blockedMouse
            content: bind content  
        }  
    }  
     override attribute onMousePressed = function(e: MouseEvent): Void {  
         startX = e.getDragX()-endX;  
         startY = e.getDragY()-endY;  
     }  
   
     override attribute onMouseDragged = function(e: MouseEvent): Void {  
         endX = e.getDragX()-startX;  
         endY = e.getDragY()-startY;  
     } 
     
     override attribute onMouseEntered = function(e: MouseEvent): Void {
         cursor = Cursor.HAND;
     }
}
